from genshin_elements import *
from cheaters import *
from math import sin, cos, sqrt ,pi, floor

def key_to_char(event):
    num_char = r")!@#$%^&*("
    if(event.mod & pygame.KMOD_SHIFT):
        if  (event.key >= pygame.K_0 and event.key <= pygame.K_9):
            return num_char[event.key - pygame.K_0]
        elif (event.key >= pygame.K_a and event.key <= pygame.K_z):
            return pygame.key.name(event.key).upper()
        else:
            return pygame.key.name(event.key)

    else:
        return pygame.key.name(event.key)

def linspace(a, b, n):
    step = (b - a)/(n - 1)
    result = [0] * n
    for j in range(n):
        result[j] = a + j*step
    return result

class GAMEPHASE:
    BEGIN = 0
    MAIN = 1
    WIN = 2
    LOSE = 3
    CREATING = 4

class ElemCapability:
    elem_node: GenshinElemNode
    elem_stack: list
    active: False
    effective: False
    stack_depth:int 
    def __init__(self, elem_type, pos, radius, stack_depth:int = 5) -> None:
        self.elem_stack = []
        self.elem_node = GenshinElemNode(elem_type, pos, radius)
        self.active = False
        self.effective = False
        if(elem_type == ELEMENT_TYPE.TIME):
            self.stack_depth = 7
        else:
            self.stack_depth = stack_depth

    def activate(self):
        self.active = True

    def append(self, new_elem):
        if(len(self.elem_stack) < 7):
            if(self.elem_node.elem_type == ELEMENT_TYPE.TIME):
                if(new_elem < 7):
                    if(not self.elem_stack.count(new_elem)):
                        self.elem_stack.append(new_elem)
            else:
                if(self.elem_node.elem_type == new_elem):
                    self.elem_stack.append(new_elem)
            if(len(self.elem_stack) >= self.stack_depth):
                self.activate()

    def do_something(self, app):
        pass

    def on_click(self):
        self.effective = True
        self.active = False
        self.elem_stack = []

    def __call__(self, app) -> None:
        if self.effective:
            self.do_something(app)
        
    def render(self, window:pygame.Surface):
        x = self.elem_node.pos[0]
        y_top = self.elem_node.pos[1]
        radius = self.elem_node.radius*2

        x_left = x - radius/4 
        y_elems_top = y_top + self.elem_node.radius - 10
        y_curr = y_elems_top + (self.stack_depth - 1) * radius

        for elem in self.elem_stack:
            rect = pygame.Rect((x_left, y_curr, radius/2, radius))
            pygame.draw.rect(window, color=custum_settings['elem_color'][elem], rect=rect)

            y_curr -= radius
        
        for j in range(self.stack_depth - len(self.elem_stack)):
            rect = pygame.Rect((x_left, y_curr, radius/2, radius))
            pygame.draw.rect(window, color=custum_settings['elem_color'][7], rect=rect)
            y_curr -= radius
        
        # plot elemtype button
        if self.active:
            curr_img = pygame.image.load('./resources/pictures/capa/%s.png'%(ELEMENT_TYPE.type_name(self.elem_node.elem_type)))
            rect = curr_img.get_rect(center = self.elem_node.pos)
            window.blit(curr_img, rect)
        else:
            curr_img = pygame.image.load('./resources/pictures/capa/%s_hidden.png'%(ELEMENT_TYPE.type_name(self.elem_node.elem_type)))
            rect = curr_img.get_rect(center = self.elem_node.pos)
            window.blit(curr_img, rect)

class MovoPower(ElemCapability):
    num_blow: int
    def __init__(self, pos, radius, stack_depth: int = 5) -> None:
        super().__init__(ELEMENT_TYPE.MOVO, pos, radius, stack_depth)
        self.num_blow = custum_settings['movo_power_num']
    
    def do_nothing(self, app):
        print("ehi!")

    def do_something(self, app):
        pile = app.pile
        num_blow = min([len(pile.all_nodes), self.num_blow])

        # get last num_blow terms
        new_stack = []
        blowed_items = pile.all_nodes[-num_blow:]
        while blowed_items:
            blowed_items_copy = blowed_items.copy()
            for term in blowed_items_copy:
                if term.is_top():
                    new_stack.append(term)
                    blowed_items.remove(term)
                    pile.remove_top(term)
        while new_stack:
            node = new_stack.pop()
            x = (rand() - 0.5) * app.pile_sidelength + 2*app.pile_center
            y = (rand() - 0.5) * app.pile_sidelength + app.pile_center
            node.pos = (x,y)
            pile.add_top(node, overlap_cmp_fun)

        self.effective = False

class SadaPower(ElemCapability):
    def __init__(self, pos, radius, stack_depth: int = 5) -> None:
        super().__init__(ELEMENT_TYPE.SADA, pos, radius, stack_depth)
    
    def do_something(self, app):
        pos_left = app.stack_rect.x + app.stack_rect.width + 50
        pos_y = app.stack_rect.y + app.stack_rect.height/2
        curr_x = pos_left
        for j in range(3):
            new_elem = randint(ELEMENT_TYPE.MAX_ID)
            for k in range(3):
                node = GenshinElemNode(new_elem, (curr_x, pos_y), app.radius)
                curr_x += 2*app.radius + 10
                app.pile.add_top(node, overlap_cmp_fun)

        self.effective = False

class NiniPower(ElemCapability):
    def __init__(self, pos, radius, stack_depth: int = 5) -> None:
        super().__init__(ELEMENT_TYPE.NINI, pos, radius, stack_depth)
    
    def do_something(self, app):
        pos_left = app.stack_rect.x + app.stack_rect.width + 50
        pos_y = app.stack_rect.y + app.stack_rect.height/2
        curr_x = pos_left
        
        new_items = []
        for j in range(3):
            if(app.elem_stack.elem_list):
                curr_elem = app.elem_stack.elem_list[0]
                new_items.append(curr_elem)
                app.elem_stack.elem_count[curr_elem] -= 1
                if(app.elem_stack.elem_count[curr_elem] == 0):
                    app.elem_stack.elem_list.pop(0)
        
        for new_elem in new_items:
            node = GenshinElemNode(new_elem, (curr_x, pos_y), app.radius)
            curr_x += 2*app.radius + 10
            app.pile.add_top(node, overlap_cmp_fun)

        self.effective = False

class TimePower(ElemCapability):
    n_ticks: int
    remaining_ticks: int

    is_food: bool

    def __init__(self, pos, radius, stack_depth: int = 5) -> None:
        super().__init__(ELEMENT_TYPE.TIME, pos, radius, stack_depth)
        self.n_ticks = 5 * custum_settings['frame_rate']
        self.remaining_ticks = self.n_ticks
        self.is_food = False
    
    def activate(self):
        self.remaining_ticks = self.n_ticks
        super().activate()

    def do_something(self, app):
        app.game_phase = GAMEPHASE.MAIN
        self.remaining_ticks -= 1
        if(self.remaining_ticks == 0):
            self.effective = False

    def render(self, window: pygame.Surface):
        super().render(window)
        if(self.effective):
            s = pygame.Surface((window.get_width(), window.get_height()))  # the size of your rect
            s.set_alpha(128)                # alpha level
            s.fill((0,0,0))           # this fills the entire surface
            window.blit(s, (0,0))    # (0,0) are the top-left coordinates

    def who_are_you(self):
        if(self.is_food):
            return "emergency food"
        else:
            return "best friend"


class GushaPower(ElemCapability):
    def __init__(self, pos, radius, stack_depth: int = 3) -> None:
        super().__init__(ELEMENT_TYPE.GUSHA, pos, radius, stack_depth)
    
    def activate(self):
        self.effective = True
        super().activate()

    def on_click(self):
        pass

    def do_something(self, app):
        if(app.game_phase == GAMEPHASE.LOSE):
            app.game_phase = GAMEPHASE.MAIN
            pos_left = app.stack_rect.x + app.stack_rect.width + 50
            pos_y = app.stack_rect.y + app.stack_rect.height/2
            curr_x = pos_left
            
            new_items = []
            for j in range(3):
                if(app.elem_stack.elem_list):
                    curr_elem = app.elem_stack.elem_list[0]
                    new_items.append(curr_elem)
                    app.elem_stack.elem_count[curr_elem] -= 1
                    if(app.elem_stack.elem_count[curr_elem] == 0):
                        app.elem_stack.elem_list.pop(0)
            
            for new_elem in new_items:
                node = GenshinElemNode(new_elem, (curr_x, pos_y), app.radius)
                curr_x += 2*app.radius + 10
                app.pile.add_top(node, overlap_cmp_fun)

            self.effective = False
            self.active = False
            self.elem_stack = []




class PowerSelector:
    time_lim: int
    power_list: list
    cheat_code_active:bool = True
    cheat_code: CheatCode
    cheater_list: list
    cheater:str
    def __init__(self, left, right,  y, radius, capa_list=custum_settings['curr_capa'], 
                cheater_list=ALL_CHEATERS, time_lim=2 * custum_settings['frame_rate']) -> None:
        N_powers = len(capa_list)
        x_list = linspace(left, right, N_powers + 2)
        self.power_list = []
        for j in range(N_powers):
            node = GenshinElemNode(capa_list[j], (x_list[j+1], y), radius)
            self.power_list.append(node)

        # cheat_code
        self.cheater_list = cheater_list
        self.time_lim = time_lim
        self.reinit()
   
    def reinit(self):
        self.cheater = ''
        self.cheat_code_active = True
        self.cheat_code = CheatCode(self.cheater_list, self.time_lim)
 
    def __call__(self):
        self.cheat_code.time_pass()

    def on_click(self, pos, app):
        for node in self.power_list:
            if pos_on_elem(pos, node):
                app.cheater = set_cheater(self.cheater)
                return node.elem_type
        return -1

    def on_press(self, key_event):
        if(self.cheat_code_active):
            if((key_event.key >= pygame.K_0 and key_event.key <= pygame.K_9) or (key_event.key >= pygame.K_a and key_event.key <= pygame.K_z)):
                id = self.cheat_code.add_letter(key_to_char(key_event))
                if(id >= 0):
                    self.cheater = self.cheater_list[id]
                    self.cheat_code_active = False
                    return True
        return False

    def render(self, window:pygame.Surface):
        for node in self.power_list:
            curr_img = pygame.image.load('./resources/pictures/capa/%s.png'%(ELEMENT_TYPE.type_name(node.elem_type)))
            rect = curr_img.get_rect(center = node.pos)
            window.blit(curr_img, rect)
